﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using HMH;

namespace SameGame.Model
{
	internal class CBoard : IBoard
	{
		public event EventHandler ScoreChanged;
		public event EventHandler GameIsOver;
		public event EventHandler ItemsChanged;
		public event EventHandler BoardInited;

		protected virtual void OnScoreChanged(EventArgs e)
		{
			if(ScoreChanged != null) {
				ScoreChanged(this, e);
			}
		}
		
		protected virtual void OnGameIsOver(EventArgs e)
		{
			if(GameIsOver != null) {
				GameIsOver(this, e);
			}
		}

		protected virtual void OnItemsChanged()
		{
			if (ItemsChanged != null) {
				ItemsChanged(this, EventArgs.Empty);
			}
		}

		protected virtual void OnBoardInited()
		{
			if (BoardInited != null) {
				BoardInited(this, EventArgs.Empty);
			}
		}
		
		public CBoard()
		{
			mBalls = new CItem[0];

			Width = 0;
			Height = 0;
			_frame = new CFrame();

			HMH.Logging.LogSettings logSet = new HMH.Logging.LogSettings();
			//logSet.Load(false);
			logSet.Level = HMH.Logging.LogLevel.INFO;
			logSet.LogType = "console";
			
			logSet.DebugColor = ConsoleColor.White;
			logSet.ErrorColor = ConsoleColor.Red;
			logSet.FatalColor = ConsoleColor.Red;
			logSet.InfoColor = ConsoleColor.Gray;
			logSet.WarningColor = ConsoleColor.Yellow;

			logSet.Save();
			HMH.Logging.LogSystem.Init(logSet);
			HMH.Logging.ILog logger = HMH.Logging.LogSystem.Instance;

			logger = HMH.Logging.LogSystem.Instance;
		}

		public CBoard(int _columns, int _rows, CItem[] balls) : this()
		{
			HMH.Logging.LogSystem.Instance.Info("");
			mBalls = balls;

			Width = _columns;
			Height = _rows;
		}

		public void Init(int _columns, int _rows, int _colors = 3)
		{
			HMH.Logging.LogSystem.Instance.Info("");
			Width = _columns;
			Height = _rows;
			_score = 0;

			mBalls = new CItem[Width * Height];

			for(int column = 0; column < Width; ++column) {
				for(int row = 0; row < Height; ++row) {
					CItem ball = new CItem();
					ball._color = rndGenerator.Next(_colors);
					ball.Location = new CItemLocation(column, row);
					ball.PreviousLocation = new CItemLocation(-1, -1);
					mBalls[indexOf(column, row)] = ball;
				}
			}
			OnBoardInited();
			OnScoreChanged(EventArgs.Empty);
		}

		public CItem getItem(int column, int row)
		{
			if(!isValidPosition(column, row)) {
				return null;
			}

			return mBalls[indexOf(column, row)];
		}

		public CItem getItem(CItemLocation pos)
		{
			return getItem(pos.x, pos.y);
		}

		public IEnumerable<CItem> getAllItems()
		{
			return mBalls;
		}

		public IEnumerable<CItem> getItems(int row, int column)
		{
			return mBalls;
		}

		public IEnumerable<CItem> getItems(CItemLocation pos)
		{
			return getItems(pos.x, pos.y);
		}

		public bool isValidPosition(int column, int row)
		{
			return ((row >= 0) && (row < Height)) && ((column >= 0) && (column < Width))
				&& (mBalls[indexOf(column, row)] != null);
		}

		public bool isValidPosition(CItemLocation loc)
		{
			return isValidPosition(loc.x, loc.y);
		}
		
		public bool Highlight(int column, int row)
		{
			if(!isValidPosition(column, row)) return false;

			FloodFill(column, row, -1);

			if(_fillFound != 0) {
				foreach(int index in _groupIndex) {
					mBalls[index]._index = index;
					_frame.AddHighlighted(mBalls[index]);
				}
			}
			
			return (_fillFound != 0) && (_groupIndex.Count > 1);
		}

		public bool Remove(int column, int row)
		{
			bool result = Highlight(column, row);

			if(result) {
				ShuffleDown();
				
				// TODO: check if game is over
				_isGameOver = CheckVictory();
				if(_isGameOver) {
					HMH.Logging.LogSystem.Instance.Info(String.Format("Game is over!!!"));
					OnGameIsOver(EventArgs.Empty);
				}

				// update score
				_score += _frame.Score;
				if(_isBonus) {
					_score += BonusValue; // Bonus value should be evaluated later from the board size settings
				}
				HMH.Logging.LogSystem.Instance.Info(String.Format("Current score is {0}", Score));
				OnScoreChanged(EventArgs.Empty);

				OnItemsChanged();
			}

			return result;
		}

		public CFrame getLastFrame()
		{
			return _frame;
		}

		#region properties

		public CItem this[int x, int y]
		{
			get
			{
				return getItem(x, y);
			}
		}

		public int Score
		{
			get { return _score; }
		}

		public bool GameOver
		{
			get { return _isGameOver;  }
		}

		public int Width { get; set; }
		public int Height { get; set; }

		#endregion

		#region Private Functions

		private int indexOf(int column, int row)
		{
			return column + row * Width;
		}

		private void FloodFill(int column, int row, int color)
		{
			if(!isValidPosition(column, row)) return;

			if(mBalls[indexOf(column, row)] == null) return;

			_first = false;
			if(-1 == color) {
				// first iteration of group search procedure - initialize all variables
				_first = true;
				color = mBalls[indexOf(column, row)]._color;

				_fillFound = 0;
				_frame.Clear();
				_groupIndex.Clear();

				foreach(CItem ball in mBalls) {
					if(ball != null) {
						ball.Checked = false;
					}
				}
			}

			CItem _currentBall = mBalls[indexOf(column, row)];

			// TODO: check if this condition is still valid
			if(-1 == _currentBall._color) return;

			if(_currentBall.Checked || (!_first && color != _currentBall._color)) return;

			_currentBall.Checked = true;

			FloodFill(column + 1, row,     color);
			FloodFill(column - 1, row,     color);
			FloodFill(column,     row + 1, color);
			FloodFill(column,     row - 1, color);

			if(_first && !(_fillFound == 0)) return;

			_groupIndex.Add(indexOf(column, row));
			_fillFound += 1;
		}

		private bool CheckVictory()
		{
			_isBonus = !isValidPosition(0, Height - 1);

			return _isBonus || !FloodCheck(0, Height - 1, -1);
		}

		private bool FloodCheck(int column, int row, int color)
		{
			if(!isValidPosition(column, row)) {
				return false;
			}

			int currentBallColor = mBalls[indexOf(column, row)]._color;

			if(currentBallColor == color) {
					return true;
			}

			return FloodCheck(column, row - 1, currentBallColor) || FloodCheck(column + 1, row, currentBallColor);
		}

		void ShuffleDown()
		{
			HMH.Logging.LogSystem.Instance.Dbg("ShuffleDown");

			foreach(CItem ball in _frame.getHighlighted()) {
				mBalls[ball._index] = null;
			}

			foreach(CItem ball in mBalls) {
				if(ball != null) {
					ball.PreviousLocation.Set(ball.Location.x, ball.Location.y);
				}
			}
			
			int _fallDistance = 0;
			for(int col = 0; col < Width; ++col) {
				_fallDistance = 0;
				for(int row = Height - 1; row >= 0; --row) {
					if(mBalls[indexOf(col, row)] == null) {
						HMH.Logging.LogSystem.Instance.Dbg(String.Format("Gap on position ({0},{1})", col, row));
						_fallDistance++;
					} else {
						if(_fallDistance > 0) {
							HMH.Logging.LogSystem.Instance.Dbg(String.Format("Move position ({0},{1}) to ({2},{3})", col, row, col, row + _fallDistance));
							mBalls[indexOf(col, row + _fallDistance)] = mBalls[indexOf(col, row)];

							mBalls[indexOf(col, row + _fallDistance)].Location.Set(col, row + _fallDistance);
							_frame.AddReplaced(mBalls[indexOf(col, row + _fallDistance)]);

							mBalls[indexOf(col, row)] = null;
						}
					}
				}
			}

			_fallDistance = 0;
			for(int col = 0; col < Width; ++col) {
				if(mBalls[indexOf(col, Height - 1)] == null) {
					HMH.Logging.LogSystem.Instance.Dbg(String.Format("Gap on position ({0},{1})", col, Height - 1));
					_fallDistance++;
				} else {
					HMH.Logging.LogSystem.Instance.Dbg(String.Format("Start move on column ({0}), shift on {1} positions", col, _fallDistance));
					if(_fallDistance > 0) {
						for(int row = 0; row < Height; ++row) {
							if(mBalls[indexOf(col, row)] != null) {
								HMH.Logging.LogSystem.Instance.Dbg(String.Format("Move position ({0},{1}) to ({2},{3})", col, row, col - _fallDistance, row));
								mBalls[indexOf(col - _fallDistance, row)] = mBalls[indexOf(col, row)];

								mBalls[indexOf(col - _fallDistance, row)].Location.Set(col - _fallDistance, row);
								_frame.AddReplaced(mBalls[indexOf(col - _fallDistance, row)]);

								mBalls[indexOf(col, row)] = null;
							}
						}
					}
				}
			}
		}

		#endregion

		#region Private Members

		private CItem[] mBalls;
		private CFrame _frame;
		private Random rndGenerator = new Random();

		private bool _first = false;
		private uint _fillFound = 0;
		private List<int> _groupIndex = new List<int>();

		private int _score = 0;
		private bool _isGameOver = false;
		private bool _isBonus = false;

		private const int BonusValue = 500;

		#endregion
	}

	public class CItemLocation
	{
		public CItemLocation(int _x, int _y)
		{
			x = _x;
			y = _y;
		}

		public void Set(int _x, int _y)
		{
			x = _x;
			y = _y;
		}

		public int x;
		public int y;

		public override string ToString()
		{
			return string.Format("ItemLocation: x={0}, y={1}", x, y);
		}
	}
}
